{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "77gENRVX40S7"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "d8jyt37T42Vf"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "aPxHdjwW5P2j"
      },
      "outputs": [],
      "source": [
        "#@title MIT License\n",
        "#\n",
        "# Copyright (c) 2017 François Chollet                                                                                                                    # IGNORE_COPYRIGHT: cleared by OSS licensing\n",
        "#\n",
        "# Permission is hereby granted, free of charge, to any person obtaining a\n",
        "# copy of this software and associated documentation files (the \"Software\"),\n",
        "# to deal in the Software without restriction, including without limitation\n",
        "# the rights to use, copy, modify, merge, publish, distribute, sublicense,\n",
        "# and/or sell copies of the Software, and to permit persons to whom the\n",
        "# Software is furnished to do so, subject to the following conditions:\n",
        "#\n",
        "# The above copyright notice and this permission notice shall be included in\n",
        "# all copies or substantial portions of the Software.\n",
        "#\n",
        "# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n",
        "# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n",
        "# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n",
        "# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n",
        "# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n",
        "# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n",
        "# DEALINGS IN THE SOFTWARE."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hRTa3Ee15WsJ"
      },
      "source": [
        "# 転移学習とファインチューニング"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dQHMcypT3vDT"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/tutorials/images/transfer_learning\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org で表示</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ja/tutorials/images/transfer_learning.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab で実行</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ja/tutorials/images/transfer_learning.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub でソースを表示</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ja/tutorials/images/transfer_learning.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">ノートブックをダウンロード</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2X4KyhORdSeO"
      },
      "source": [
        "このチュートリアルでは、転移学習を使用して、事前トレーニング済みネットワークから猫や犬の画像を分類する方法を紹介します。\n",
        "\n",
        "事前トレーニング済みモデルは、通常は大規模な画像分類タスクなどの大規模なデータセットで事前トレーニング済みの保存されたネットワークです。事前トレーニング済みモデルをそのまま使用したり、転移学習を使用してこのモデルを任意のタスクにカスタマイズしたりすることができます。\n",
        "\n",
        "画像分類のための転移学習の背後にある考え方は、モデルが大規模かつ十分に一般的なデータセットでトレーニングされていれば、そのモデルは視覚世界の一般的なモデルとして効果的に機能するというものです。それにより、最初から大規模なデータセット上で大規模なモデルをトレーニングを行わずに、これらの学習した特徴マップを活用することができます。\n",
        "\n",
        "このノートブックでは、トレーニング済みモデルをカスタマイズする 2 つの方法を試します。\n",
        "\n",
        "1. 特徴抽出：前のネットワークで学習した表現を使用して、新しいサンプルから意味のある特徴を抽出します。事前トレーニング済みモデルの上に新規にトレーニングされる新しい分類器を追加するだけで、データセットで前に学習した特徴マップを再利用できるようになります。\n",
        "\n",
        "モデル全体を（再）トレーニングする必要はありません。ベースとなる畳み込みネットワークには、画像分類に一般的に有用な特徴がすでに含まれています。ただし、事前トレーニング済みモデルの最後の分類部分は元の分類タスクに固有で、その後はモデルがトレーニングされたクラスのセットに固有です。\n",
        "\n",
        "1. ファインチューニング：凍結された基本モデルの最上位レイヤーのいくつかを解凍し、新たに追加された分類器レイヤーと解凍した基本モデルの最後のレイヤーの両方を合わせてトレーニングします。これにより、基本モデルの高次の特徴表現を「ファインチューニング」して、特定のタスクにより関連性を持たせることができます。\n",
        "\n",
        "一般的な機械学習のワークフローに従います。\n",
        "\n",
        "1. データを調べ、理解する\n",
        "2. 入力パイプラインを構築し、この場合は Keras ImageDataGenerator を使用する\n",
        "3. モデルを作成する\n",
        "    - 事前トレーニング済みの基本モデル（および事前トレーニング済みの重み）を読み込む\n",
        "    - 分類レイヤーを上に重ねる\n",
        "4. モデルをトレーニングする\n",
        "5. モデルを評価する\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gjWM9Lkqm0C1"
      },
      "outputs": [],
      "source": [
        "!pip install tf-nightly"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "TqOt6Sv7AsMi"
      },
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import os\n",
        "import tensorflow as tf\n",
        "\n",
        "from tensorflow.keras.preprocessing import image_dataset_from_directory"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "v77rlkCKW0IJ"
      },
      "source": [
        "## データの前処理をする"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0GoKGm1duzgk"
      },
      "source": [
        "### データをダウンロードする"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vHP9qMJxt2oz"
      },
      "source": [
        "このチュートリアルでは、数千枚の犬猫の画像を含むデータセットを使用します。画像を含む zip ファイルをダウンロードして解凍した後、`tf.keras.preprocessing.image_dataset_from_directory` ユーティリティを使用して `tf.data.Dataset` を作成し、トレーニングと検証を行います。画像の読み込みに関する詳細は、この[チュートリアル](https://www.tensorflow.org/tutorials/load_data/images)をご覧ください。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ro4oYaEmxe4r"
      },
      "outputs": [],
      "source": [
        "_URL = 'https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip'\n",
        "path_to_zip = tf.keras.utils.get_file('cats_and_dogs.zip', origin=_URL, extract=True)\n",
        "PATH = os.path.join(os.path.dirname(path_to_zip), 'cats_and_dogs_filtered')\n",
        "\n",
        "train_dir = os.path.join(PATH, 'train')\n",
        "validation_dir = os.path.join(PATH, 'validation')\n",
        "\n",
        "BATCH_SIZE = 32\n",
        "IMG_SIZE = (160, 160)\n",
        "\n",
        "train_dataset = image_dataset_from_directory(train_dir,\n",
        "                                             shuffle=True,\n",
        "                                             batch_size=BATCH_SIZE,\n",
        "                                             image_size=IMG_SIZE)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "cAvtLwi7_J__"
      },
      "outputs": [],
      "source": [
        "validation_dataset = image_dataset_from_directory(validation_dir,\n",
        "                                                  shuffle=True,\n",
        "                                                  batch_size=BATCH_SIZE,\n",
        "                                                  image_size=IMG_SIZE)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "yO1Q2JaW5sIy"
      },
      "source": [
        "トレーニングセットの最初の 9 枚の画像とラベルを表示します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "K5BeQyKThC_Y"
      },
      "outputs": [],
      "source": [
        "class_names = train_dataset.class_names\n",
        "\n",
        "plt.figure(figsize=(10, 10))\n",
        "for images, labels in train_dataset.take(1):\n",
        "  for i in range(9):\n",
        "    ax = plt.subplot(3, 3, i + 1)\n",
        "    plt.imshow(images[i].numpy().astype(\"uint8\"))\n",
        "    plt.title(class_names[labels[i]])\n",
        "    plt.axis(\"off\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EZqCX_mpV3Mx"
      },
      "source": [
        "元のデータセットにはテストセットが含まれていないので、テストセットを作成します。作成には、`tf.data.experimental.cardinality` を使用して検証セットで利用可能なデータのバッチ数を調べ、そのうちの 20％ をテストセットに移動します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "uFFIYrTFV9RO"
      },
      "outputs": [],
      "source": [
        "val_batches = tf.data.experimental.cardinality(validation_dataset)\n",
        "test_dataset = validation_dataset.take(val_batches // 5)\n",
        "validation_dataset = validation_dataset.skip(val_batches // 5)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Q9pFlFWgBKgH"
      },
      "outputs": [],
      "source": [
        "print('Number of validation batches: %d' % tf.data.experimental.cardinality(validation_dataset))\n",
        "print('Number of test batches: %d' % tf.data.experimental.cardinality(test_dataset))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MakSrdd--RKg"
      },
      "source": [
        "### パフォーマンスのためにデータセットを構成する"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "22XWC7yjkZu4"
      },
      "source": [
        "バッファ付きプリフェッチを使用して、I/O のブロッキングなしでディスクから画像を読み込みます。この手法の詳細については[データパフォーマンス](https://www.tensorflow.org/guide/data_performance)ガイドをご覧ください。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "p3UUPdm86LNC"
      },
      "outputs": [],
      "source": [
        "AUTOTUNE = tf.data.experimental.AUTOTUNE\n",
        "\n",
        "train_dataset = train_dataset.prefetch(buffer_size=AUTOTUNE)\n",
        "validation_dataset = validation_dataset.prefetch(buffer_size=AUTOTUNE)\n",
        "test_dataset = test_dataset.prefetch(buffer_size=AUTOTUNE)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MYfcVwYLiR98"
      },
      "source": [
        "### データ増強を使用する"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bDWc5Oad1daX"
      },
      "source": [
        "大規模な画像データセットを持っていない場合には、回転や水平反転など、ランダムでありながら現実的な変換をトレーニング画像に適用し、サンプルを人為的に多様化することをお勧めします。これによって、トレーニングデータのさまざまな側面にモデルを露出させることができ、[過適合](https://www.tensorflow.org/tutorials/keras/overfit_and_underfit)を遅らせることができます。データ増強についての詳細は、この[チュートリアル](https://www.tensorflow.org/tutorials/images/data_augmentation)をご覧ください。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3P99QiMGit1A"
      },
      "outputs": [],
      "source": [
        "data_augmentation = tf.keras.Sequential([\n",
        "  tf.keras.layers.experimental.preprocessing.RandomFlip('horizontal'),\n",
        "  tf.keras.layers.experimental.preprocessing.RandomRotation(0.2),\n",
        "])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "s9SlcbhrarOO"
      },
      "source": [
        "注意: これらのレイヤーは、トレーニング中に `model.fit` を呼び出した場合にのみアクティブです。モデルが `model.evaulate` または `model.fit` の推論モードで使用されている場合には非アクティブです。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9mD3rE2Lm7-d"
      },
      "source": [
        "これらのレイヤーを同じ画像に繰り返して適用し、結果を見てみましょう。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "aQullOUHkm67"
      },
      "outputs": [],
      "source": [
        "for image, _ in train_dataset.take(1):\n",
        "  plt.figure(figsize=(10, 10))\n",
        "  first_image = image[0]\n",
        "  for i in range(9):\n",
        "    ax = plt.subplot(3, 3, i + 1)\n",
        "    augmented_image = data_augmentation(tf.expand_dims(first_image, 0))\n",
        "    plt.imshow(augmented_image[0] / 255)\n",
        "    plt.axis('off')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bAywKtuVn8uK"
      },
      "source": [
        "### ピクセル値をリスケールする\n",
        "\n",
        "すぐに `tf.keras.applications.MobileNetV2` をダウンロードして、基本モデルとして使用します。このモデルはピクセル値 `[-1,1]` を想定していますが、この時点での画像のピクセル値は `[0-255]` です。ピクセル値のリスケールには、モデルに含まれる前処理のメソッドを使用します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "cO0HM9JAQUFq"
      },
      "outputs": [],
      "source": [
        "preprocess_input = tf.keras.applications.mobilenet_v2.preprocess_input"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xnr81qRMzcs5"
      },
      "source": [
        "注意: 別の方法として、[Rescaling](https://www.tensorflow.org/api_docs/python/tf/keras/layers/experimental/preprocessing/Rescaling) レイヤーを使用して、ピクセル値を `[0,255]` から `[-1,1]` にリスケールすることも可能です。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "R2NyJn4KQMux"
      },
      "outputs": [],
      "source": [
        "rescale = tf.keras.layers.experimental.preprocessing.Rescaling(1./127.5, offset= -1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Wz7qgImhTxw4"
      },
      "source": [
        "注意: 他の `tf.keras.applications` を使用する場合は、API ドキュメントを確認してピクセル値が `[-1,1]` または `[0,1]` を要求しているか確かめるか、あるいは含まれている関数 `preprocess_input` を使用します。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "OkH-kazQecHB"
      },
      "source": [
        "## 事前トレーニング済み畳み込みニューラルネットワークから基本モデルを作成する\n",
        "\n",
        "Google が開発した **MobileNet V2** モデルから基本モデルを作成します。これは、140 万枚の画像と 1000 クラスで構成された大規模データセットである ImageNet データセットによる事前トレーニング済みのモデルです。ImageNet は、`jackfruit` や `syringe` のような多彩なカテゴリを持つ研究用トレーニングデータセットです。この知識の基盤が、特定のデータセットから猫と犬を分類するのに有用になります。\n",
        "\n",
        "まず、特徴抽出に使用する MobileNet V2 のレイヤーを選択する必要があります。最後の分類レイヤー（ほとんどの機械学習モデルの図では、下から上に向かうため、「上」にあります）はあまり役には立ちません。 その代わりに、平坦化演算の前の最後のレイヤーに依存するのが一般的です。このレイヤーは「ボトルネックレイヤー」と呼ばれます。ボトルネックレイヤーの特徴には、最終/最上位レイヤーよりも一般性が保持されています。\n",
        "\n",
        "はじめに、ImageNet でトレーニングした重みで事前に読み込んだ MobileNet V2 モデルをインスタンス化します。引数 **include_top=False** を指定して、上位の分類レイヤーを含まない、特徴抽出に理想的なネットワークを読み込みます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "19IQ2gqneqmS"
      },
      "outputs": [],
      "source": [
        "# Create the base model from the pre-trained model MobileNet V2\n",
        "IMG_SHAPE = IMG_SIZE + (3,)\n",
        "base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE,\n",
        "                                               include_top=False,\n",
        "                                               weights='imagenet')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AqcsxoJIEVXZ"
      },
      "source": [
        "この特徴抽出器は、各 `160x160x3` の画像を `5x5x1280` の特徴ブロックに変換します。 これで画像のバッチ例がどうなるかを見てみましょう。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Y-2LJL0EEUcx"
      },
      "outputs": [],
      "source": [
        "image_batch, label_batch = next(iter(train_dataset))\n",
        "feature_batch = base_model(image_batch)\n",
        "print(feature_batch.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rlx56nQtfe8Y"
      },
      "source": [
        "## 特徴を抽出する\n",
        "\n",
        "このステップでは、前のステップで作成した畳み込みベースを凍結させ、特徴抽出器として使用します。さらに、その上に分類器を追加して、最上位の分類器のトレーニングを行います。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CnMLieHBCwil"
      },
      "source": [
        "### 畳み込みベースを凍結させる"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7fL6upiN3ekS"
      },
      "source": [
        "モデルをコンパイルしてトレーニングする前に、畳み込みベースを凍結させることは重要です。（layer.trainable = False と設定して）凍結させると、トレーニング中に特定のレイヤーの重みは更新されなくなります。MobileNet V2 には多くのレイヤーがありますが、モデル全体の `trainable` フラグを False に設定すれば、すべてのレイヤーをまとめて凍結することができます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "OTCJH4bphOeo"
      },
      "outputs": [],
      "source": [
        "base_model.trainable = False"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jsNHwpm7BeVM"
      },
      "source": [
        "### BatchNormalization レイヤーに関する重要な注意事項\n",
        "\n",
        "多くのモデルには `tf.keras.layers.BatchNormalization` レイヤーが含まれています。このレイヤーは特殊なケースで、ファインチューニングのコンテキストに注意を払う必要があります。このチュートリアルでも後ほど説明します。\n",
        "\n",
        "`layer.trainable = False` と設定する場合、`BatchNormalization` レイヤーは推論モードで実行されるため、その平均と分散統計は更新されません。\n",
        "\n",
        "ファインチューニングを行うために BatchNormalization（バッチ正規化）レイヤーを含んだモデルを解凍する場合には、基本モデルを呼び出す際に `training = False` を渡して BatchNormalization レイヤーを推論モードにしておく必要があります。そうしないと、トレーニング不可能な重みに更新が適用され、モデルが学習したものを破壊してしまいます。\n",
        "\n",
        "詳細については[転移学習ガイド](https://www.tensorflow.org/guide/keras/transfer_learning)をご覧ください。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "KpbzSmPkDa-N"
      },
      "outputs": [],
      "source": [
        "# Let's take a look at the base model architecture\n",
        "base_model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wdMRM8YModbk"
      },
      "source": [
        "### 分類ヘッドを追加する"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QBc31c4tMOdH"
      },
      "source": [
        "特徴ブロックから予測値を生成するには、`f.keras.layers.GlobalAveragePooling2D` レイヤーを使用して `5x5` 空間の空間位置を平均化し、特徴を画像ごとに単一の1280要素ベクトルに変換します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "dLnpMF5KOALm"
      },
      "outputs": [],
      "source": [
        "global_average_layer = tf.keras.layers.GlobalAveragePooling2D()\n",
        "feature_batch_average = global_average_layer(feature_batch)\n",
        "print(feature_batch_average.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "O1p0OJBR6dOT"
      },
      "source": [
        "`tf.keras.layers.Dense` レイヤーを適用して、これらの特徴を画像ごとに単一の予測値に変換します。この予測は `logit` または未加工の予測値として扱われるため、ここで活性化関数は必要ありません。 正の数はクラス 1 を予測し、負の数はクラス 0 を予測します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Wv4afXKj6cVa"
      },
      "outputs": [],
      "source": [
        "prediction_layer = tf.keras.layers.Dense(1)\n",
        "prediction_batch = prediction_layer(feature_batch_average)\n",
        "print(prediction_batch.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HXvz-ZkTa9b3"
      },
      "source": [
        "[Keras Functional API](https://www.tensorflow.org/guide/keras/functional) を使用して、データ増強、リスケール、base_model、特徴抽出レイヤーを連結してモデルを構築します。前に触れたように、モデルには BatchNormalization レイヤーが含まれているため、training=False を使用します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "DgzQX6Veb2WT"
      },
      "outputs": [],
      "source": [
        "inputs = tf.keras.Input(shape=(160, 160, 3))\n",
        "x = data_augmentation(inputs)\n",
        "x = preprocess_input(x)\n",
        "x = base_model(x, training=False)\n",
        "x = global_average_layer(x)\n",
        "x = tf.keras.layers.Dropout(0.2)(x)\n",
        "outputs = prediction_layer(x)\n",
        "model = tf.keras.Model(inputs, outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "g0ylJXE_kRLi"
      },
      "source": [
        "### モデルをコンパイルする\n",
        "\n",
        "トレーニングする前にモデルをコンパイルします。2 つのクラスがあり、モデルが線形出力を提供するので、`from_logits=True` のニ値交差エントロピー損失を使用します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "RpR8HdyMhukJ"
      },
      "outputs": [],
      "source": [
        "base_learning_rate = 0.0001\n",
        "model.compile(optimizer=tf.keras.optimizers.Adam(lr=base_learning_rate),\n",
        "              loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n",
        "              metrics=['accuracy'])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "I8ARiyMFsgbH"
      },
      "outputs": [],
      "source": [
        "model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lxOcmVr0ydFZ"
      },
      "source": [
        "MobileNet の 250 万個のパラメータは凍結されていますが、Dense レイヤーには 1200 個の*トレーニング可能な*パラメータがあります。 これらは 2 つの `tf.Variable` オブジェクトである、重みとバイアスに分割されます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "krvBumovycVA"
      },
      "outputs": [],
      "source": [
        "len(model.trainable_variables)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RxvgOYTDSWTx"
      },
      "source": [
        "### モデルをトレーニングする\n",
        "\n",
        "10 エポックのトレーニング後は、検証セットの精度が最大 94%になります。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Om4O3EESkab1"
      },
      "outputs": [],
      "source": [
        "initial_epochs = 10\n",
        "\n",
        "loss0, accuracy0 = model.evaluate(validation_dataset)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8cYT1c48CuSd"
      },
      "outputs": [],
      "source": [
        "print(\"initial loss: {:.2f}\".format(loss0))\n",
        "print(\"initial accuracy: {:.2f}\".format(accuracy0))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "JsaRFlZ9B6WK"
      },
      "outputs": [],
      "source": [
        "history = model.fit(train_dataset,\n",
        "                    epochs=initial_epochs,\n",
        "                    validation_data=validation_dataset)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Hd94CKImf8vi"
      },
      "source": [
        "### 学習曲線\n",
        "\n",
        "MobileNet V2 基本モデルを固定の特徴抽出器として使用する場合の、トレーニングと検証それぞれの精度と損失の学習曲線を見てみましょう。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "53OTCh3jnbwV"
      },
      "outputs": [],
      "source": [
        "acc = history.history['accuracy']\n",
        "val_acc = history.history['val_accuracy']\n",
        "\n",
        "loss = history.history['loss']\n",
        "val_loss = history.history['val_loss']\n",
        "\n",
        "plt.figure(figsize=(8, 8))\n",
        "plt.subplot(2, 1, 1)\n",
        "plt.plot(acc, label='Training Accuracy')\n",
        "plt.plot(val_acc, label='Validation Accuracy')\n",
        "plt.legend(loc='lower right')\n",
        "plt.ylabel('Accuracy')\n",
        "plt.ylim([min(plt.ylim()),1])\n",
        "plt.title('Training and Validation Accuracy')\n",
        "\n",
        "plt.subplot(2, 1, 2)\n",
        "plt.plot(loss, label='Training Loss')\n",
        "plt.plot(val_loss, label='Validation Loss')\n",
        "plt.legend(loc='upper right')\n",
        "plt.ylabel('Cross Entropy')\n",
        "plt.ylim([0,1.0])\n",
        "plt.title('Training and Validation Loss')\n",
        "plt.xlabel('epoch')\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "foWMyyUHbc1j"
      },
      "source": [
        "注意: 明らかに検証指標がトレーニング指標よりも優れていることを疑問に思われるかもしれませんが、それはトレーニング中に `tf.keras.layer.BatchNormalization` や `tf.keras.layer.Dropout` などのレイヤーが精度に影響を与えていることが主な要因です。検証損失の計算時には、これらのレイヤーはオフになっています。\n",
        "\n",
        "また、上記の要因ほどではないにせよトレーニング指標がエポックの平均を報告する一方で検証指標がエポック後に評価されるため、検証指標の方が少しだけ長い時間トレーニングされたモデルを参照しているという理由もあります。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CqwV-CRdS6Nv"
      },
      "source": [
        "## ファインチューニング\n",
        "\n",
        "特徴抽出の実験では、MobileNet V2 基本モデル上に少数のレイヤーを重ねてトレーニングをしたに過ぎませんでした。事前トレーニング済みネットワークの重みはトレーニング中に更新**されません**。\n",
        "\n",
        "パフォーマンスをさらに向上させる方法の 1 つに、追加した分類器のトレーニングと並行して、事前トレーニング済みモデルの最上位レイヤーの重みをトレーニング（または「ファインチューニング」）するというものがあります。トレーニングのプロセスでは、一般的な特徴マップから特にデータセットに関連付けられた特徴に、強制的に重みをチューニングします。\n",
        "\n",
        "注意: これは必ず、事前トレーニング済みモデルをトレーニング不可に設定し、最上位の分類器をトレーニングした後に行うようにしてください。事前トレーニング済みモデルの上にランダムに初期化された分類器を追加してすべてのレイヤーを結合トレーニングしようとすると、（分類器からのランダムな重みにより）勾配の更新規模が大きすぎて、事前トレーニング済みモデルが学習したことを忘れてしまいます。\n",
        "\n",
        "また、MobileNet モデル全体ではなく、少数の最上位レイヤーをファインチューニングしてみることをお勧めします。大部分の畳み込みネットワークでは、上位のレイヤーに行くほど専門性が高くなります。最初の数レイヤーは、ほとんどすべてのタイプの画像に一般化する、非常に単純かつ一般的な特徴を学習します。上位のレイヤーに行くに従って、特徴はモデルがトレーニングされたデータセットに対してもっと固有なものになっていきます。ファインチューニングの目的は、一般的な学習を上書きするのではなく、これらの特殊な特徴に適応させて新しいデータセットで作業ができるようにすることです。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CPXnzUK0QonF"
      },
      "source": [
        "### モデルの最上位レイヤーを解凍する\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rfxv_ifotQak"
      },
      "source": [
        "ここでは `base_model` を解凍して、最下位レイヤーをトレーニング不可設定にするだけです。その後、モデルを再コンパイルして（これは行った変更を有効化するために必要）、トレーニングを再開します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4nzcagVitLQm"
      },
      "outputs": [],
      "source": [
        "base_model.trainable = True"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "-4HgVAacRs5v"
      },
      "outputs": [],
      "source": [
        "# Let's take a look to see how many layers are in the base model\n",
        "print(\"Number of layers in the base model: \", len(base_model.layers))\n",
        "\n",
        "# Fine-tune from this layer onwards\n",
        "fine_tune_at = 100\n",
        "\n",
        "# Freeze all the layers before the `fine_tune_at` layer\n",
        "for layer in base_model.layers[:fine_tune_at]:\n",
        "  layer.trainable =  False"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4Uk1dgsxT0IS"
      },
      "source": [
        "### モデルをコンパイルする\n",
        "\n",
        "かなり大規模なモデルをトレーニングしているため、事前トレーニング済みの重みを再適用する場合は、この段階では低い学習率を使用することが重要です。そうしなければ、モデルがすぐに過適合を起こす可能性があります。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NtUnaz0WUDva"
      },
      "outputs": [],
      "source": [
        "model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n",
        "              optimizer = tf.keras.optimizers.RMSprop(lr=base_learning_rate/10),\n",
        "              metrics=['accuracy'])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "WwBWy7J2kZvA"
      },
      "outputs": [],
      "source": [
        "model.summary()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "bNXelbMQtonr"
      },
      "outputs": [],
      "source": [
        "len(model.trainable_variables)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4G5O4jd6TuAG"
      },
      "source": [
        "### モデルのトレーニングを続ける"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0foWUN-yDLo_"
      },
      "source": [
        "前に収束するようにトレーニングをした場合は、このステップを踏むと精度が数ポイント向上します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ECQLkAsFTlun"
      },
      "outputs": [],
      "source": [
        "fine_tune_epochs = 10\n",
        "total_epochs =  initial_epochs + fine_tune_epochs\n",
        "\n",
        "history_fine = model.fit(train_dataset,\n",
        "                         epochs=total_epochs,\n",
        "                         initial_epoch=history.epoch[-1],\n",
        "                         validation_data=validation_dataset)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "TfXEmsxQf6eP"
      },
      "source": [
        "MobileNet V2 基本モデルの最後の数レイヤーをファインチューニングし、その上で分類器をトレーニングした場合の、トレーニングと検証それぞれの精度と損失の学習曲線を見てみましょう。検証損失の曲線がトレーニング損失よりもはるかに高いため、多少の過適合が発生する可能性があります。\n",
        "\n",
        "また、新しいトレーニングセットが比較的小規模で、元の MobileNet V2 データセットに似ているため、過適合が発生する場合があります。\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DNtfNZKlInGT"
      },
      "source": [
        "モデルをファインチューニングした後は、検証セットの精度はほぼ 98%に達します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "PpA8PlpQKygw"
      },
      "outputs": [],
      "source": [
        "acc += history_fine.history['accuracy']\n",
        "val_acc += history_fine.history['val_accuracy']\n",
        "\n",
        "loss += history_fine.history['loss']\n",
        "val_loss += history_fine.history['val_loss']"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "chW103JUItdk"
      },
      "outputs": [],
      "source": [
        "plt.figure(figsize=(8, 8))\n",
        "plt.subplot(2, 1, 1)\n",
        "plt.plot(acc, label='Training Accuracy')\n",
        "plt.plot(val_acc, label='Validation Accuracy')\n",
        "plt.ylim([0.8, 1])\n",
        "plt.plot([initial_epochs-1,initial_epochs-1],\n",
        "          plt.ylim(), label='Start Fine Tuning')\n",
        "plt.legend(loc='lower right')\n",
        "plt.title('Training and Validation Accuracy')\n",
        "\n",
        "plt.subplot(2, 1, 2)\n",
        "plt.plot(loss, label='Training Loss')\n",
        "plt.plot(val_loss, label='Validation Loss')\n",
        "plt.ylim([0, 1.0])\n",
        "plt.plot([initial_epochs-1,initial_epochs-1],\n",
        "         plt.ylim(), label='Start Fine Tuning')\n",
        "plt.legend(loc='upper right')\n",
        "plt.title('Training and Validation Loss')\n",
        "plt.xlabel('epoch')\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "R6cWgjgfrsn5"
      },
      "source": [
        "### 評価と予測をする"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PSXH7PRMxOi5"
      },
      "source": [
        "最後に、テストセットを使用して、新しいデータでモデルの性能を検証することができます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "2KyNhagHwfar"
      },
      "outputs": [],
      "source": [
        "loss, accuracy = model.evaluate(test_dataset)\n",
        "print('Test accuracy :', accuracy)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8UjS5ukZfOcR"
      },
      "source": [
        "これで、このモデルを使用してペットが猫か犬かを予測する準備がすべて整いました。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "RUNoQNgtfNgt"
      },
      "outputs": [],
      "source": [
        "#Retrieve a batch of images from the test set\n",
        "image_batch, label_batch = test_dataset.as_numpy_iterator().next()\n",
        "predictions = model.predict_on_batch(image_batch).flatten()\n",
        "\n",
        "# Apply a sigmoid since our model returns logits\n",
        "predictions = tf.nn.sigmoid(predictions)\n",
        "predictions = tf.where(predictions &lt; 0.5, 0, 1)\n",
        "\n",
        "print('Predictions:\\n', predictions.numpy())\n",
        "print('Labels:\\n', label_batch)\n",
        "\n",
        "plt.figure(figsize=(10, 10))\n",
        "for i in range(9):\n",
        "  ax = plt.subplot(3, 3, i + 1)\n",
        "  plt.imshow(image_batch[i].astype(\"uint8\"))\n",
        "  plt.title(class_names[predictions[i]])\n",
        "  plt.axis(\"off\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_TZTwG7nhm0C"
      },
      "source": [
        "## 要約\n",
        "\n",
        "- **特徴抽出に事前トレーニング済みモデルを使用する**:  小さなデータセットで作業する場合は、より大規模な同じドメインのデータセットでトレーニングされたモデルが学習した特徴を利用するのが一般的です。これは、事前トレーニング済みモデルをインスタンス化し、その上に完全に接続された分類器を追加して行います。事前トレーニング済みモデルは「凍結」されているため、トレーニング中は分類器の重みだけを更新します。この場合、畳み込みベースは各画像に関連付けられたすべての特徴を抽出し、抽出された特徴量のセットから画像クラスを決定する分類器をトレーニングします。\n",
        "\n",
        "- **事前トレーニング済みモデルをファインチューニングする**: 性能をさらに向上させるために、事前トレーニング済みモデルの最上位レイヤーをファインチューニングして、新しいデータセットに再利用することができます。この場合は、データセット固有の高レベルの特徴をモデルが学習するように重みをチューニングします。この手法は、通常はトレーニングデータセットが大規模で、事前トレーニング済みモデルがトレーニングされた元のデータセットによく似ている場合に推奨されます。\n",
        "\n",
        "さらに詳しくは[転移学習ガイド](https://www.tensorflow.org/guide/keras/transfer_learning)をご覧ください。\n"
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "collapsed_sections": [],
      "name": "transfer_learning.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
